<!DOCTYPE html>
<html lang="en" xmlns:v-on="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>双向绑定</title>
    <style>
        #app {
            text-align: center;
            margin-top: 100px;
            color: #888;
        }

        h1 {
            color: #aaa;
        }

        input {
            padding: 0 10px;
            width: 600px;
            line-height: 2.5;
            border: 1px solid #ccc;
            border-radius: 2px;
        }

        .bind {
            color: #766;
        }

        strong {
            color: #05BC00;
        }

        button {
            padding: 5px 10px;
            border: 1px solid #777777;
            border-radius: 5px;
            background: #ffffff;
            color: #777777;
            cursor: pointer;

        }
    </style>
</head>
<body>

<div id="app">
    <h1>Hi，MVVM</h1>
    <input v-model="name" placeholder="请输入内容" type="text">
    <h1 class="bind">{{name}} 's age is <strong>{{age}}</strong></h1>
    <button v-on:click="sayHi">点击欢迎您</button>
</div>

<script>
    let id = 0;
    let currentObserver = null;

    function observe(data) {
        // 如果不是一个对象就终止
        if (!data || typeof data !== 'object') {
            return false;
        }
        for (let key in data) {
            let val = data[key];
            let subject = new Subject();

            // 绑定监听
            Object.defineProperty(data, key, {
                enumerable: true,
                configurable: true,
                get: function () {
                    if (currentObserver) {
                        currentObserver.subscribeTo(subject);
                    }
                    return val;
                },
                set: function (newValue) {
                    val = newValue;
                    subject.notify();
                }
            });

            if (typeof val === 'object') {
                observe(val)
            }
        }


    }

    /*订阅者*/
    class Subject {
        constructor() {
            this.id = id++;
            this.observers = [];
        }

        addObserver(observer) {
            this.observers.push(observer);
        }

        removeObserver(observer) {
            let index = this.observers.indexOf(observer);
            if (index !== -1) {
                this.observers.splice(index, 1)
            }
        }

        notify() {
            this.observers.forEach(observer => {
                observer.update()
            })
        }
    }

    /*观察者*/
    class Observer {
        constructor(vm, key, cb) {
            this.subjects = {};
            this.vm = vm;
            this.key = key;
            this.cb = cb;
            this.value = this.getValue();
        }

        // 如果新旧数据不同，更新数据
        update() {
            let oldValue = this.value;
            let value = this.getValue();
            if (oldValue !== value) {
                this.value = value;
                this.cb.bind(this.vm)(value, oldValue)
            }
        }

        // 添加观察者
        subscribeTo(subject) {
            if (!this.subjects[subject.id]) {
                subject.addObserver(this);
                this.subjects[subject.id] = subject;
            }
        }

        // 获取Value
        getValue() {
            currentObserver = this;
            let value = this.vm.$data[this.key];
            currentObserver = null;
            return value;
        }
    }

    /*编译对象*/
    class Compile {
        constructor(vm) {
            this.vm = vm;
            this.node = vm.$el;
            console.log(this.node);
            this.compile();
        }

        compile() {
            this.traverse(this.node)
        }

        traverse(node) {
            if (node.nodeType === 1) {
                this.compileNode(node);
                node.childNodes.forEach(childNode => {
                    this.traverse(childNode)
                })
            } else if (node.nodeType === 3) {
                this.compileText(node);
            }
        }

        // 文本编译
        compileText(node) {
            let reg = /{{(.+?)}}/g;
            let match;
            while (match = reg.exec(node.nodeValue)) {
                let raw = match[0];
                let key = match[1].trim();                  // 索引
                node.nodeValue = node.nodeValue.replace(raw, this.vm.$data[key]);
                new Observer(this.vm, key, function (val, oldVal) {
                    node.nodeValue = node.nodeValue.replace(oldVal, val)
                })
            }
        }

        // 节点编译
        compileNode(node) {
            let attrs = [...node.attributes];
            attrs.forEach(attr => {
                if (this.isModelDirective(attr.name)) {
                    this.bindModel(node, attr);
                } else if (this.isEventDirective(attr.name)) {
                    this.bindEventHandler(node, attr)
                }
            })
        }

        // 双向绑定
        bindModel(node, attr) {
            let key = attr.value;
            node.value = this.vm.$data[key];
            new Observer(this.vm, key, function (newValue) {
                node.value = newValue
            });
            node.oninput = (e) => {
                console.log(e.target.value);
                this.vm.$data[key] = e.target.value;
            }
        }

        // 绑定事件
        bindEventHandler(node, attr) {
            let eventType = attr.name.substr(5);
            let methodName = attr.value;
            node.addEventListener(eventType, this.vm.$methods[methodName]);
        }

        isModelDirective(attrName) {
            return attrName === 'v-model'
        }

        isEventDirective(attrName) {
            return attrName.indexOf('v-on') === 0;
        }
    }

    class Mvvm {
        constructor(opts) {
            this.init(opts);
            observe(this.$data);
            new Compile(this);      //变异当前对象
        }

        init(opts) {
            if (opts.el.nodeType === 1) {
                this.$el = opts.el;
            } else {
                this.$el = document.querySelector(opts.el)
            }

            this.$data = opts.data || {};
            this.$methods = opts.methods || {};
            for (let key in this.$data) {
                Object.defineProperty(this, key, {
                    enumerable: true,
                    configurable: true,
                    get: () => {
                        return this.$data[key]
                    },
                    set: (newValue) => {
                        this.$data[key] = newValue;
                    }
                })
            }

            for (let key in this.$methods) {
                this.$methods[key] = this.$methods[key].bind(this);
            }
        }
    }

    let vm = new Mvvm({
        el: '#app',
        data: {
            name: 'yanle',
            age: 3
        },
        methods: {
            sayHi: function () {
                alert(`hello ${this.name}`)
            }
        }
    });

    let clock = setInterval(function () {
        vm.age++;
        if (vm.age === 10) {
            clearInterval(clock)
        }
    }, 1000)


</script>


</body>
</html>